package client;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;

import dataContracts.InstallSettings;
import UI.UI;
import dataContracts.CommandParser;
import dataContracts.CommandType;
import dataContracts.FileState;

public class ClientProgram {
	InstallSettings settings;
	Socket socket;
	BufferedReader reader;
	InputStream binaryReader;
	PrintWriter writer;
	OutputStream binaryWriter;
	private UI ui;
	private MessageHandler msgHandler;
	
	ClientDirectory directory;
	
	public ClientProgram(InstallSettings settings) {
		this.settings = settings;
	}

	public static void main(String[] args) throws IOException {
		InstallSettings s = InstallSettings.getSettings();
		ClientProgram program = new ClientProgram(s);
		if (program.environmentIsConfigured()) {
			program.run();
		} else {
			System.out.println("You're environment is not configured. Create the following directories:");
			//System.out.println("\t"+clientDirectory);
		}
	}
	
	private boolean environmentIsConfigured() {
		boolean test = new File(settings.directoryPath).isDirectory();
		File folder = new File(settings.directoryPath);
		return (folder.exists() && folder.isDirectory());
	}

	public void run() {
		directory = new ClientDirectory(settings.directoryPath);
		MessageHandler msgHandler = new MessageHandler(this);
		ui = new UI(msgHandler, directory);
		
		configureSocket();
		
		String message = "";
		try {
			while ((message = reader.readLine()) != null) {
				// parse out the 'body' portion of the message (i.e. just remove the command type prefix)
				String commandBody = CommandParser.getCommandText(CommandParser.getCommandType(message), message);
				
				switch (CommandParser.getCommandType(message)) {
				case NewFile:
					// this triggers the new file download
					msgHandler.GetFile(commandBody);
					break;
				case UpdateFile:
					//TODO: add logic to ask user to confirm overwrite first?
					msgHandler.GetFile(commandBody);
					break;
				case DeleteFile:
					// TODO: only make this change if hasLocalChanges() returns false
					directory.removeFile(commandBody);
					break;
				case ResolveDiff:
					// get the local file reference...
					ClientFile cf = directory.findFile(commandBody);
					if (cf.fileState == FileState.Added) {
						// tell the server to prepare for upload
						msgHandler.RequestUpload(commandBody);	
						ui.servLog("Resolved: Upload "+ commandBody);
					} else {
						// if it wasn't added locally, remove it...
						directory.removeFile(commandBody);
						ui.servLog("Resolved: Remove "+ commandBody);
					}
					
					break;
				case GetFile:
					// upload the file
					String[] args = commandBody.split(":");
					String file = args[0], port = args[1];
					DownloadClient client = new DownloadClient(file, settings.serverAddress, Integer.parseInt(port), directory);
					client.operation = "upload";
					Thread t = new Thread(client);
					t.start();
					
					ui.servLog("Uploading file: "+ file);
					
					break;
				case RecvFile:
					// this kicks off a new thread which does the download...
					// TODO: how can we get feedback in the ui when the download is done? (try passing UI or client program to thread)
					// TODO: add more error checking around bad responses...
					String[] args1 = commandBody.split(":");
					String file1 = args1[0], port1 = args1[1];
					
					Thread t1 = new Thread(new DownloadClient(file1, settings.serverAddress, Integer.parseInt(port1), directory));
					t1.start();
					
					ui.servLog("Downloading file: "+ file1);
					
					break;
				case Broadcast:
					// TODO: why can't I run this command through the MessageHandler like: msgHandler.AddLogMsg(message);
					ui.servLog(commandBody);
					break;
					
				case MarkAsCurrent:
					// this merely ensures that any outdated flags (updated, added, etc) have been cleared
					ClientFile curr = directory.findFile(commandBody);
					if (curr != null)
						curr.fileState = FileState.Synchronized;
					
					break;
				case IsEqual:
					if (commandBody.equals("1")) { //equal
						ui.servLog(("Directories are in sync"));
					} else if (commandBody.equals("0")) { //NOT equal
						ui.servLog("Directories are NOT in sync");
						
						// request a synch from the server
						msgHandler.RequestSynch();
					} else {
						ui.servLog("There was a problem checking for differences ("+commandBody+")...");
					}
					
					break;
				case Synch:
					// nothing really happens here, since the server process kicks off the following commands
					// directly from the server-side Synch call: (see above case statements for how they're handled) 
					//  - NewFile
					// 	- DeleteFile
					//	- UpdateFile
					
					break;
				default:
					ui.servLog("Unknown command from server: "+ message);
					break;
				}
			}
		} catch (IOException e) { e.printStackTrace(); }
		
		ui.run();
	}
	
	public String getFileList() {
		String files = "";
		
		//TODO: how do we avoid having to do this? (it reloads the current state of the directory form the disk)
		directory.refresh();
		
		for (ClientFile file : directory.files) {
			files += file.getName() + ":" + file.getChecksum() + ";";
		}
		
		// trim off the trailing pipe character so we can just string.split() on the server
		if (files.length() > 0)
			files = files.substring(0, files.length()-1);
		
		return files;
	}

	private void configureSocket() {
		try {
			// create socket, and configure read/write streams
			socket = new Socket(settings.serverAddress, settings.serverPort);
			InputStreamReader _reader = new InputStreamReader(socket.getInputStream());
			reader = new BufferedReader(_reader);
			writer = new PrintWriter(socket.getOutputStream());
			binaryWriter = socket.getOutputStream();
			binaryReader = socket.getInputStream();
			
			System.out.println("Connected to server... ");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}